use bits::{BitsOps, IntoBits};

pub enum Code {
    InstAddrMisaligned = 0,
    InstAccessFault,
    IllegalInst,
    BreakPoint,
    LoadAddressMissaligned,
    LoadAccessFault,
    StoreAmoAddressMissaligned,
    StoreAmoAccessFault,
    EnvCallFromUmode,
    EnvCallFromSmode = 9,
    // Exception 10: reserved
    EnvCallFromMmode = 11,
    InstPageFault = 12,
    LoadPageFault = 13,
    // Exception 14: reserved
    StoreAmoPageFault = 15,
}

/// 异常号，不编码最高 bit，最高 bit 的编码由 [`Cause`](crate::register::machine::mcause::Cause) 来进行
pub enum Exception {
    Core(Code),
    Reserved(usize),
    Custom(usize),
}

impl TryFrom<usize> for Exception {
    type Error = &'static str;

    fn try_from(value: usize) -> Result<Self, Self::Error> {
        Ok(match value {
            0 => Exception::Core(Code::InstAddrMisaligned),
            1 => Exception::Core(Code::InstAccessFault),
            2 => Exception::Core(Code::IllegalInst),
            3 => Exception::Core(Code::BreakPoint),
            4 => Exception::Core(Code::LoadAddressMissaligned),
            5 => Exception::Core(Code::LoadAccessFault),
            6 => Exception::Core(Code::StoreAmoAddressMissaligned),
            7 => Exception::Core(Code::StoreAmoAccessFault),
            8 => Exception::Core(Code::EnvCallFromUmode),
            9 => Exception::Core(Code::EnvCallFromSmode),
            11 => Exception::Core(Code::EnvCallFromMmode),
            12 => Exception::Core(Code::InstPageFault),
            13 => Exception::Core(Code::LoadPageFault),
            15 => Exception::Core(Code::StoreAmoPageFault),
            24..=31 | 48..=63 => Exception::Custom(value),
            _ => {
                if value.bits(..).msb() {
                    Err("msb must not be set")?
                } else {
                    Exception::Reserved(value)
                }
            }
        })
    }
}

impl Into<usize> for Exception {
    fn into(self) -> usize {
        match self {
            Exception::Core(x) => x as usize,
            Exception::Reserved(x) | Exception::Custom(x) => x,
        }
    }
}
